/******************************************************************************************************
Copyright (C) 2019 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals Molecule, Atom, Str*/
/*jslint plusplus: true, indent: 4*/


function MolecularAssignmentsParser(aMolecule) {
	"use strict";
	this.molecule = new Molecule(aMolecule);
	this.originalAssignments = this.molecule.nmrAssignments();
	this.assignments = JSON.parse(JSON.stringify(this.originalAssignments));
	if (this.assignments) {
		this.joinEquivalentProtons();
		this.addLabelsAndNotation();
		this.addLabileAndQuality();
	}
}


MolecularAssignmentsParser.prototype.addLabelsAndNotation = function () {
	"use strict";

	var i, atom;

	for (i = 0; i < this.assignments.length; i++) {
		atom = this.molecule.atom(this.assignments[i].atom.index);

		this.assignments[i].atom.number = atom.number;
		this.assignments[i].atom.protonNotation = atom.protonNotation;
		this.assignments[i].atom.label = this.getAtomLabel(this.assignments[i]);
		this.assignments[i].atom.explicit = (this.assignments[i].element === atom.elementSymbol);
		this.assignments[i].atom.labelNMReDATA = ((this.assignments[i].element === "H" && !this.assignments[i].atom.explicit) ? "H" : "") + this.assignments[i].atom.label;
		this.assignments[i].atom.indexNMReDATA = ((this.assignments[i].element === "H" && !this.assignments[i].atom.explicit) ? "H" : "") + this.assignments[i].atom.index;
	}
};

MolecularAssignmentsParser.prototype.addLabileAndQuality = function () {
	"use strict";

	var i, atom, atomIndexFromMol;

	for (i = 0; i < this.assignments.length; i++) {
		atom = this.molecule.atom(this.assignments[i].atom.index);
		atomIndexFromMol = this.assignments[i].atom.index;
		this.assignments[i].atom.labile = this.molecule.isLabileProton(atomIndexFromMol);
		this.assignments[i].atom.quality = this.molecule.atom(atomIndexFromMol).assignmentQuality;
	}
};

MolecularAssignmentsParser.prototype.joinEquivalentProtons = function () {
	"use strict";

	var i, j, assigAtIndex, protAssigAtIndex, numberOfEqProtons, alreadyAddedProton, notEqProton,
		newAssignments = [];

	for (i = 1; i <= this.molecule.atomCount; i++) {
		assigAtIndex = this.getFromAtomIndex(i);
		numberOfEqProtons = undefined;
		notEqProton = false;

		if (assigAtIndex.length) {
			protAssigAtIndex = this.getFromAtomElement("H", assigAtIndex);
			if (protAssigAtIndex.length === 1) {
				numberOfEqProtons = 1;
			} else if (protAssigAtIndex.length === 2) {
				if (JSON.stringify(protAssigAtIndex[0].shift) === JSON.stringify(protAssigAtIndex[1].shift)) {
					numberOfEqProtons = 2;
				} else {
					notEqProton = true;
				}
			} else if (protAssigAtIndex.length > 2) {
				numberOfEqProtons = protAssigAtIndex.length;
			}
		}

		alreadyAddedProton = false;
		for (j = 0; j < assigAtIndex.length; j++) {
			if (assigAtIndex[j].element !== "H" || !numberOfEqProtons) { //always add heavy atoms or not equivalent protons
				if (notEqProton && assigAtIndex[j].element === "H") {
					assigAtIndex[j].atom.notEqProton = true;
				}
				newAssignments.push(assigAtIndex[j]);
			} else { //if equivalent protons are present, add only first element
				assigAtIndex[j].numberOfEqAtoms = numberOfEqProtons;
				if (!alreadyAddedProton) {
					newAssignments.push(assigAtIndex[j]);
					alreadyAddedProton = true;
				}
			}
		}
	}
	this.assignments = newAssignments;
};


MolecularAssignmentsParser.prototype.getAll = function () {
	"use strict";

	return JSON.parse(JSON.stringify(this.assignments));
};


MolecularAssignmentsParser.prototype.getFromAtom = function (aAtom, aAssignments) {
	"use strict";

	var i;

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].atom.index === aAtom.index) {
			if (aAtom.h && aAtom.h === aAssignments[i].atom.h) {
				return JSON.parse(JSON.stringify(aAssignments[i]));
			}
			if (!aAtom.h && !aAssignments[i].atom.h) {
				return JSON.parse(JSON.stringify(aAssignments[i]));
			}
		}
	}

	return undefined;
};


MolecularAssignmentsParser.prototype.getFromAtomIndex = function (aIndex, aAssignments) {
	"use strict";

	var i, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].atom.index === aIndex) {
			aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
		}
	}

	return aArray;
};


MolecularAssignmentsParser.prototype.getFromAtomElement = function (aElement, aAssignments) {
	"use strict";

	var i, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].element === aElement) {
			aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
		}
	}
	return aArray;
};


MolecularAssignmentsParser.prototype.getFromShiftUUID = function (aUUID, aAssignments) {
	"use strict";

	var i, j, k, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].shift && aAssignments[i].shift.length) {
			for (j = 0; j < aAssignments[i].shift.length; j++) {
				if (aAssignments[i].shift[j].idTypes && aAssignments[i].shift[j].idTypes.length) {
					for (k = 0; k <  aAssignments[i].shift[j].idTypes.length; k++) {
						if (aAssignments[i].shift[j].idTypes[k].uuid === aUUID) {
							aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
						}
					}
				}
			}
		}
	}
	return aArray;
};


MolecularAssignmentsParser.prototype.getFromAtomLabel = function (aLabel, aAssignments) {
	"use strict";

	var i, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].atom.label === aLabel) {
			aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
		}
	}
	return aArray;
};


MolecularAssignmentsParser.prototype.getFromAtomLabelNMReDATA = function (aLabelNMReDATA, aAssignments) {
	"use strict";

	var i, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		if (aAssignments[i].atom.labelNMReDATA === aLabelNMReDATA) {
			aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
		}
	}
	return aArray;
};


MolecularAssignmentsParser.prototype.getFromShiftType = function (aType, aAssignments) {
	"use strict";

	var i, j, k, aArray = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		for (j = 0; j < aAssignments[i].shift.length; j++) {
			if (aAssignments[i].shift[j].idTypes && aAssignments[i].shift[j].idTypes.length) {
				for (k = 0; k < aAssignments[i].shift[j].idTypes.length; k++) {
					if (aAssignments[i].shift[j].idTypes[k].type === aType) {
						aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
					}
				}
			}
		}
	}

	return aArray;
};


MolecularAssignmentsParser.prototype.getFromShift = function (aShift, aAssignments) {
	"use strict";

	var i, j, diff, aArray = [], precision = 0.05;

	if (!aAssignments) {
		aAssignments = this.assignments;
		precision = 0.01;
	}

	for (i = 0; i < aAssignments.length; i++) {
		for (j = 0; j < this.assignments[i].shift.length; j++) {
			diff = Math.abs(aAssignments[i].shift[j].shift - aShift);
			if (diff < precision) {
				aArray.push(JSON.parse(JSON.stringify(aAssignments[i])));
			}
		}
	}
	return aArray;
};


MolecularAssignmentsParser.prototype.getAtomLabel = function (aAssignment) {
	'use strict';

	var notationIndex,
		label = aAssignment.atom.number || aAssignment.atom.index.toString();

	if (aAssignment.element === "H" && aAssignment.atom.notEqProton) {
		notationIndex = this.baseNotationToIndex(aAssignment.atom.h);
		label += this.toCurrentNotation(Str.hIndexToLetter(notationIndex), aAssignment.atom.protonNotation);
	}
	return label;
};


MolecularAssignmentsParser.prototype.toCurrentNotation = function (aQuote, aNotation) {
	'use strict';

	switch (aNotation) {

	case Atom.QUOTES:
		return aQuote;
	case Atom.AB:
		return aQuote === "'" ? "a" : "b";
	case Atom.ALPHABETA:
		return aQuote === "'" ? "alpha" : "beta";
	case Atom.CISTRANS:
		return aQuote === "'" ? "cis" : "trans";
	case Atom.AXEQ:
		return aQuote === "'" ? "ax" : "eq";
	default:
		return aQuote;
	}
};


MolecularAssignmentsParser.prototype.baseNotationToIndex = function (aBaseNotation) {
	'use strict';

	switch (aBaseNotation) {

	case "a":
		return 1;
	case "b":
		return 2;
	case "c":
		return 3;
	}
};


MolecularAssignmentsParser.prototype.correctProtonAssignments = function (aAssignments) {
	'use strict';

	var i, aAtom, aCorrected, secondCorrected,
		correctedAssignments = [];

	if (!aAssignments) {
		aAssignments = this.assignments;
	}

	for (i = 0; i < aAssignments.length; i++) {
		aCorrected = MolecularAssignmentsParser.clone(aAssignments[i]);
		if (aCorrected.element === "H") {
			aAtom = this.molecule.atom(aAssignments[i].atom.index);
			switch (aAtom.nH) {
			case 0:
				correctedAssignments.push(aCorrected);
				break;
			case 1:
				aCorrected.atom.h = "a";
				correctedAssignments.push(aCorrected);
				break;
			case 2:
				if (aCorrected.atom.h) {
					aCorrected.atom.notEqProton = true;
					aCorrected.atom.label = this.getAtomLabel(aCorrected);
					correctedAssignments.push(aCorrected);
				} else {
					aCorrected.atom.h = "a";
					correctedAssignments.push(aCorrected);
					secondCorrected = MolecularAssignmentsParser.clone(aCorrected);
					secondCorrected.atom.h = "b";
					correctedAssignments.push(secondCorrected);
				}
				break;
			case 3:
				aCorrected.atom.h = "a";
				correctedAssignments.push(aCorrected);
				secondCorrected = MolecularAssignmentsParser.clone(aCorrected);
				secondCorrected.atom.h = "b";
				correctedAssignments.push(secondCorrected);
				secondCorrected = MolecularAssignmentsParser.clone(aCorrected);
				secondCorrected.atom.h = "c";
				correctedAssignments.push(secondCorrected);
				break;
			}
		} else {
			correctedAssignments.push(aCorrected);
		}
	}
	this.assignments = correctedAssignments;
};


MolecularAssignmentsParser.prototype.cleanAssignments = function () {
	'use strict';

	var i;

	for (i = 0; i < this.assignments.length; i++) {
		try {
			delete this.assignments[i].atom.number;
			delete this.assignments[i].atom.protonNotation;
			delete this.assignments[i].atom.label;
			delete this.assignments[i].atom.explicit;
			delete this.assignments[i].atom.labelNMReDATA;
			delete this.assignments[i].atom.indexNMReDATA;
			delete this.assignments[i].atom.labile;
			delete this.assignments[i].atom.quality;
		} catch (err) {}
	}
};


MolecularAssignmentsParser.prototype.addJCouplingFromNMReDATA = function (label1, label2, aJValue) {
	"use strict";

	var i, j, k, aJElement;

	for (i = 0; i < this.assignments.length; i++) {
		if (!this.assignments[i].couplings.js) {
			this.assignments[i].couplings.js = [];
		}

		if (this.assignments[i].atom.labelNMReDATA === label1) {

			for (j = 0; j < this.assignments.length; j++) {
				if (this.assignments[j].atom.labelNMReDATA === label2) {
					aJElement = {"atom" : {"index": this.assignments[j].atom.index}, "value" : aJValue};
					if (this.assignments[j].atom.h) {
						aJElement.atom.h = this.assignments[j].atom.h;
					}
					this.assignments[i].couplings.js.push(aJElement);
				}
			}
		}

		if (this.assignments[i].atom.labelNMReDATA === label2) {
			for (k = 0; k < this.assignments.length; k++) {
				if (this.assignments[k].atom.labelNMReDATA === label1) {
					aJElement = {"atom" : {"index": this.assignments[k].atom.index}, "value" : aJValue};
					if (this.assignments[k].atom.h) {
						aJElement.atom.h = this.assignments[k].atom.h;
					}
					this.assignments[i].couplings.js.push(aJElement);
				}
			}
		}
	}
};


MolecularAssignmentsParser.prototype.addCorrelationFromNMReDATA = function (label1, label2, correlationType) {
	"use strict";

	var i, j, k, aCorrelation;

	for (i = 0; i < this.assignments.length; i++) {
		if (!this.assignments[i].correlations) {
			this.assignments[i].correlations = {};
		}
		if (!this.assignments[i].correlations[correlationType]) {
			this.assignments[i].correlations[correlationType] = [];
		}

		if (this.assignments[i].atom.labelNMReDATA === label1) {
			for (j = 0; j < this.assignments.length; j++) {
				if (this.assignments[j].atom.labelNMReDATA === label2) {
					aCorrelation = {"index": this.assignments[j].atom.index};
					if (this.assignments[j].atom.h) {
						aCorrelation.h = this.assignments[j].atom.h;
					}
					this.assignments[i].correlations[correlationType].push(aCorrelation);
				}
			}
		}

		if (this.assignments[i].atom.labelNMReDATA === label2) {
			for (k = 0; k < this.assignments.length; k++) {
				if (this.assignments[k].atom.labelNMReDATA === label1) {
					aCorrelation = {"index": this.assignments[k].atom.index};
					if (this.assignments[k].atom.h) {
						aCorrelation.h = this.assignments[k].atom.h;
					}
					this.assignments[i].correlations[correlationType].push(aCorrelation);
				}
			}
		}
	}
};


MolecularAssignmentsParser.prototype.linkMultipletToLabelNMReDATA = function (aMultiplet, aLabelNMReDATA) {
	"use strict";

	var i, multipletElement = {};

	multipletElement.type  = "Multiplet";
	multipletElement.uuid  = aMultiplet.uuid;

	for (i = 0; i < this.assignments.length; i++) {
		if (this.assignments[i].atom.labelNMReDATA === aLabelNMReDATA) {
			if (this.assignments[i].shift) {
				if (this.assignments[i].shift.length === 1) {
					if (!this.assignments[i].shift[0].idTypes) {
						this.assignments[i].shift[0].idTypes = [];
					}
					this.assignments[i].shift[0].idTypes.push(multipletElement);
				}
			}
		}
	}
};


MolecularAssignmentsParser.prototype.linkMultipletToAtomAssignment = function (aMultiplet, aAssignment) {
	"use strict";

	var i, sameHeavy, sameH,
		multipletElement = {};

	multipletElement.type  = "Multiplet";
	multipletElement.uuid  = aMultiplet.uuid;

	for (i = 0; i < this.assignments.length; i++) {
		if (this.assignments[i].atom.index === aAssignment.atom.index) {
			sameH = (this.assignments[i].atom.h && aAssignment.atom.h && this.assignments[i].atom.h === aAssignment.atom.h);
			sameHeavy = (!this.assignments[i].atom.h && !aAssignment.atom.h);
			if (sameH || sameHeavy) {
				if (this.assignments[i].shift) {
					if (this.assignments[i].shift.length === 1) {
						if (!this.assignments[i].shift[0].idTypes) {
							this.assignments[i].shift[0].idTypes = [];
						}
						this.assignments[i].shift[0].idTypes.push(multipletElement);
					}
				}
			}
		}
	}
};


MolecularAssignmentsParser.prototype.linkPeakToLabelNMReDATA = function (aPeak, aLabelNMReDATA) {
	"use strict";

	var i, peakElement = {};

	peakElement.type  = "Peak";
	peakElement.uuid  = aPeak.id;

	for (i = 0; i < this.assignments.length; i++) {
		if (this.assignments[i].atom.labelNMReDATA === aLabelNMReDATA) {
			if (this.assignments[i].shift) {
				if (this.assignments[i].shift.length === 1) {
					if (!this.assignments[i].shift[0].idTypes) {
						this.assignments[i].shift[0].idTypes = [];
					}
					this.assignments[i].shift[0].idTypes.push(peakElement);
				}
			}
		}
	}
};


MolecularAssignmentsParser.prototype.loadAssignmentsInMolecule = function () {
	'use strict';

	this.cleanAssignments();
	this.molecule.setNMRAssignments(this.assignments);
};


MolecularAssignmentsParser.clone = function (obj) {
	"use strict";

	var i, newObj, property;

	if (typeof (obj) === 'object') {
		newObj = {};
		for (property in obj) {
			if (obj[property] && typeof (obj[property]) === 'object' && obj[property].length !== undefined) {
				newObj[property] = [];
				for (i = 0; i < obj[property].length; i++) {
					newObj[property][i] = MolecularAssignmentsParser.clone(obj[property][i]);
				}
			} else {
				newObj[property] = MolecularAssignmentsParser.clone(obj[property]);
			}
		}
		return newObj;
	}
	return obj;
};

